#include <QDebug>
#include <QGraphicsSimpleTextItem>
#include <QResizeEvent>
#include <QMouseEvent>
#include <QSqlQuery>
#include <QSqlError>
#include <QMenu>
#include <QMessageBox>
#include <QScrollBar>
#include "dailywidget.h"
#include "taskeditdialog.h"
#include "sqlhelper.h"


namespace {
    QColor creatingColor(100, 100, 200, 100);
    QColor undefinedColor(210, 180, 210, 180);
}

DailyWidget::DailyWidget(QWidget *parent)
    : QGraphicsView(parent)
    , startDate_(QDate::currentDate())
    , endDate_(QDate::currentDate())
    , dateFormat_("MM/dd dddd")
    , showTimeTable_(true)
    , heightOfHour_(40)
    , timeInterval_(0.5)
    , selectingTaskID(0)
    , movingTaskID(0)
    , resizingTaskID(0)
    , copyingTaskID(0)
{
    setScene( new QGraphicsScene(this) );
    setMouseTracking(true);

    QAction* editAction = taskPopupMenu.addAction(tr("&Edit"));
    QAction* deleteAction = taskPopupMenu.addAction(tr("&Delete"));
    connect(editAction, SIGNAL(triggered()), SLOT(on_taskEdit_triggered()));
    connect(deleteAction, SIGNAL(triggered()), SLOT(on_taskDelete_triggered()));

    connect(this, SIGNAL(userIDChanged(int)), SLOT(on_userID_changed(int)));
    connect(this, SIGNAL(timeTableVisibled(bool)), SLOT(on_timeTable_visibled(bool)));
    connect(this, SIGNAL(dateChanged(QDate, QDate)), SLOT(on_date_changed(QDate, QDate)));
    connect(this, SIGNAL(dateFormatChanged(QString)), SLOT(on_dateFormat_changed(QString)));
    connect(this, SIGNAL(heightOfHourChanged(qreal)), SLOT(on_heightOfHour_changed(qreal)));

    // if horizontal scrollbar changed, move header items.
    QScrollBar* vscrollbar = verticalScrollBar();
    connect(vscrollbar, SIGNAL(valueChanged(int)), SLOT(on_vscrollbar_valueChanged(int)));
    connect(scene(), SIGNAL(sceneRectChanged(QRectF)), SLOT(on_sceneRectChanged(QRectF)));
}

void DailyWidget::resizeEvent(QResizeEvent* e)
{
    clientSize_ = e->size();
    createGraphicItems();
    setSceneRect(calendarRect_);
}

void DailyWidget::mouseMoveEvent(QMouseEvent* e)
{
    QPointF p = this->mapToScene(e->x(), e->y());

    if (creatingTaskList.empty() == false) {
        int id = 0;
        if (movingTaskID) {
            QPointF p2 = this->mapToScene(mousePressedPosition);
            QDateTime t1, t2;
            getTime(p2, t1);
            getTime(p, t2);
            int secs = t1.secsTo(t2);
            QDateTime s = taskList_[movingTaskID].stime;
            QDateTime e = taskList_[movingTaskID].etime;
            time1 = s.addSecs(secs);
            time2 = time1.addSecs(s.secsTo(e));
            id = movingTaskID;
        }
        else if (resizingTaskID) {
            getTime(p, time2);
            id = resizingTaskID;
        }
        else {
            getTime(p, time2);
        }

        qDeleteAll(creatingTaskList);
        creatingTaskList.clear();
        if (id != 0) {
            CalendarItem item = taskList_[id];
            previewTask(time1, time2, selectingColor, &item);
        }
        else
            previewTask(time1, time2, selectingColor);
    }
    else {
        movingTaskID = resizingTaskID = copyingTaskID = 0;
        int taskID;
        QList<QGraphicsItem*> items;
        getTaskItems(e->x(), e->y(), &taskID, &items);

        QCursor cur = Qt::ArrowCursor;
        foreach(QGraphicsItem* item, items) {
            if (item->data(1) != 0)
                continue;

            if (p.y() >= item->boundingRect().bottom() - 4) {
                cur = Qt::SizeVerCursor;
                resizingTaskID = taskID;
            }
            else {
                cur = Qt::OpenHandCursor;
                movingTaskID = taskID;
            }
        }
        setCursor(cur);
    }
}

void DailyWidget::mousePressEvent(QMouseEvent* e)
{
    modifiers = e->modifiers();
    getTaskItems(e->x(), e->y(), &selectingTaskID, &selectingItemList);

    if (e->buttons() & Qt::LeftButton)
        lbuttonDown(e);
    else if (e->buttons() & Qt::RightButton)
        rbuttonDown(e);
}

void DailyWidget::lbuttonDown(QMouseEvent* e)
{
    mousePressedPosition = e->pos();
    QPointF p = this->mapToScene(e->x(), e->y());

    // time label
    QFont f("Helvetica");
    f.setPixelSize(heightOfHour_ / 3.0);
    taskTime = scene()->addSimpleText("", f);

    if (selectingTaskID == 0) {
        if (getTime(p, time1) == false)
            return;
        time2 = time1;
        selectingColor = creatingColor;
        previewTask(time1, time2, selectingColor);
    }
    else {
        if (modifiers & Qt::ControlModifier) {
            int newID;
            if (copyTask(selectingTaskID, newID) == false)
                return;
            copyingTaskID = movingTaskID = selectingTaskID = newID;
            resizingTaskID = 0;
        }
        else {
            foreach (QGraphicsItem* item, selectingItemList) {
                QList<QGraphicsItem*> items = scene()->items();
                if (qFind(items, item) != items.end())
                    delete item;
            }
        }
        selectingItemList.clear();

        time1 = taskList_[selectingTaskID].stime;
        time2 = taskList_[selectingTaskID].etime;
        CalendarItem item = taskList_[selectingTaskID];
        selectingColor = item.color;
        previewTask(time1, time2, selectingColor, &item);
    }
}

void DailyWidget::rbuttonDown(QMouseEvent* e)
{
    if (selectingTaskID == 0)
        return;

    QPoint p = mapToGlobal(e->pos());
    taskPopupMenu.popup(p);
}

void DailyWidget::mouseReleaseEvent(QMouseEvent*)
{
    if (creatingTaskList.empty())
        return;

    if (selectingTaskID == 0) {
        QDateTime start = time1, end = time2;
        if (start > end)
            qSwap(start, end);

        TaskEditDialog dlg(this, db(), userID(), start, end);
        if (dlg.exec() == QDialog::Accepted) {
            updateItemList();
            createGraphicItems();
        }
        else
            qDeleteAll(creatingTaskList);
    }
    else if (movingTaskID || resizingTaskID || copyingTaskID) {
        int taskID = movingTaskID ? movingTaskID : resizingTaskID;
        CalendarItem& item = taskList_[taskID];

        if (copyingTaskID || ((item.stime != time1 || item.etime != time2) && time1 != time2)) {
            item.stime = time1;
            item.etime = time2;
            if (item.stime > item.etime)
                qSwap(item.stime, item.etime);

            if (SqlHelper().conflictTask(db_, taskID, userID_, item.stime, item.etime) == false)
                updateDatabase(item);
            else {
                if (copyingTaskID) {
                    deleteTask(taskID, false);
                }
                QMessageBox::warning(this, QString(), tr("Task is conflicting with other tasks."));
                updateItemList();
            }
        }
        createGraphicItems();
    }

    creatingTaskList.clear();

}

void DailyWidget::mouseDoubleClickEvent(QMouseEvent* e)
{
    int taskID;
    if (getTaskItems(e->x(), e->y(), &taskID, 0) == false)
        return;

    editTask(taskID);
}

void DailyWidget::createGraphicItems()
{
    scene()->clear();
    createTimeLTable();
    createDateTable();
    createTaskList();

    calendarRect_ = dateRect_ | timeRect_;
    for (int i=0; i<taskRectList_.size(); ++i)
        calendarRect_ |= taskRectList_[i];
}

void DailyWidget::createTimeLTable()
{
    timeRect_.setRect(0, 0, 0, 0);
    if (showTimeTable_ == false)
        return;

    QFont f("Helvetica");
    f.setPixelSize(heightOfHour_ / 3.0);

    qreal timeWidth = 0;
    for (int h=0; h<24; ++h){
        QString str = QString("%1:00").arg(h, 2, 10, QLatin1Char('0'));
        QGraphicsSimpleTextItem* text = scene()->addSimpleText(str, f);
        text->setBrush(QBrush(QColor(100, 100, 100)));
        qreal w = text->boundingRect().width();
        text->setPos(w, h*heightOfHour_+2);
        timeWidth = qMax(w, timeWidth);
    }
    scene()->addLine(0, 0, 0, 24*heightOfHour_, QPen(QColor(150, 150, 150)));
    timeRect_.setRect(0, 0, timeWidth*2+2, 24*heightOfHour_);
}

void DailyWidget::createDateTable()
{
    QColor color1(150, 150, 150);
    QColor color2(210, 210, 210);
    QColor holidayColor(244, 233, 233);
    QFont f("Helvetica", heightOfHour_);
    f.setPixelSize(heightOfHour_ / 2.5);

    int days = startDate_.daysTo(endDate_);
    taskRectList_.clear();
    dateTextList_.clear();

    qreal w = (clientSize_.width() - timeRect_.width()) / (days + 1);
    qreal x = timeRect_.right();

    qreal bottom = heightOfHour_ * 24;
    scene()->addLine(x, 0, x, bottom, QPen(color1));

    dateBackground_ = scene()->addRect(0, 0, 0, 0, QPen(Qt::white), QBrush(Qt::white));
    dateBackground_->setZValue(3);
    for (int i=0; i<=days; ++i) {
        scene()->addLine(x+w-1, 0, x+w-1, bottom, QPen(color1));
        QRect taskRect(x, 0, w+1, bottom);
        taskRectList_.push_back(taskRect);

        // create date label
        QDate d = startDate_.addDays(i);
        QString dateStr = d.toString(dateFormat_);
        QGraphicsSimpleTextItem* dateText = scene()->addSimpleText(dateStr, f);
        dateTextList_.push_back(dateText);
        qreal dateX = x + (w - dateText->boundingRect().width()) / 2.0;
        qreal dateY = taskRect.top() - dateText->boundingRect().height() - 2;
        dateText->setPos(dateX, dateY);
        dateText->setZValue(3);

        if (i == 0)
            dateRect_ = dateText->sceneBoundingRect();
        else
            dateRect_ |= dateText->sceneBoundingRect();

        // fill background with gray if holiday or out of working time
        QSqlQuery query = db_.exec( QString("SELECT COUNT(*) FROM HOLIDAY WHERE date='%1' or weekday=%2").arg(d.toString(Qt::ISODate)).arg(d.dayOfWeek() == 7 ? 0 : d.dayOfWeek()) );
        if (query.lastError().isValid())
            qDebug() << query.lastError().text();
        else {
            query.next();
            if (query.value(0).toInt() > 0)
                scene()->addRect(x, 0, w, bottom, QPen(), QBrush(holidayColor));
            else {
                QSqlQuery q2 = db_.exec( QString("SELECT startTime, endTime FROM USERS WHERE id='%1'").arg(userID_) );
                if (q2.lastError().isValid())
                    qDebug() << q2.lastError().text();
                else {
                    q2.next();
                    if (q2.value(0).isNull() == false) {
                        qreal b = heightOfHour_ * (q2.value(0).toTime().hour() + q2.value(0).toTime().minute() / 60.0);
                        scene()->addRect(x, 0, w, b, QPen(), QBrush(holidayColor));
                    }
                    if (q2.value(1).isNull() == false) {
                        qreal t = heightOfHour_ * (q2.value(1).toTime().hour() + q2.value(1).toTime().minute() / 60.0);
                        scene()->addRect(x, t, w, bottom-t, QPen(), QBrush(holidayColor));
                    }
                }
            }
        }

        x += w;
    }
    dateBackground_->setRect(timeRect_.right()+1, dateRect_.top(), x - timeRect_.width(), dateRect_.height());

    for (int i=0; i<49; ++i) {
        QPen pen(i % 2 == 0 ? color1 : color2);
        qreal x = i % 2 ? timeRect_.right() : timeRect_.left();
        qreal y = i * heightOfHour_ / 2;
        scene()->addLine(x, y, timeRect_.right()+w*(days+1)-1, y, pen);
    }

//    on_vscrollbar_valueChanged( verticalScrollBar()->value() );
}

void DailyWidget::createTaskList()
{
    foreach(const CalendarItem& item, taskList_)
        createTask(item);
}

void DailyWidget::createTask(const CalendarItem& item)
{
    QFont  f("helvetica");
    f.setPixelSize(heightOfHour_ / 3.0);

    QPen pen;
    QColor textColor(60, 60, 60);

    if (item.stime.date() <= endDate_ && item.etime.date() >= startDate_) {
        QList<QRectF> rects = getTaskRectList(item.stime, item.etime);
        foreach(const QRectF& r, rects) {
            // bg
            QGraphicsRectItem* taskItem = scene()->addRect(r, pen, item.color);
            taskItem->setData(1, 0);

            qreal w = r.width() / 3.0;

            // product
            GraphicsTextItemSized* prodItem = new GraphicsTextItemSized(item.product);
            prodItem->setFont(f);
            prodItem->setData(1, 1);
            prodItem->setDefaultTextColor(textColor);
            prodItem->setPos(r.left(), r.top());
            prodItem->setTextWidth(w);
            prodItem->setHeight(r.height());
            scene()->addItem(prodItem);

            // component
            GraphicsTextItemSized* compItem = new GraphicsTextItemSized(item.component);
            compItem->setFont(f);
            compItem->setData(1, 2);
            compItem->setDefaultTextColor(textColor);
            compItem->setPos(r.left()+w, r.top());
            compItem->setTextWidth(w);
            compItem->setHeight(r.height());
            scene()->addItem(compItem);

            // category
            GraphicsTextItemSized* cateItem = new GraphicsTextItemSized(item.category);
            cateItem->setFont(f);
            cateItem->setData(1, 3);
            cateItem->setDefaultTextColor(textColor);
            cateItem->setPos(r.left()+w*2, r.top());
            cateItem->setTextWidth(w);
            cateItem->setHeight(r.height());
            scene()->addItem(cateItem);

            // group task items
            QList<QGraphicsItem*> items;
            items.push_back(taskItem);
            items.push_back(prodItem);
            items.push_back(compItem);
            items.push_back(cateItem);
            QGraphicsItemGroup* group = scene()->createItemGroup(items);
            group->setData(0, item.id);
        }
    }
}

void DailyWidget::updateItemList()
{
    taskList_.clear();

    QDateTime s(startDate_);
    QDateTime e(endDate_, QTime(23, 59, 59));

    QString q = QString(
                    "SELECT"
                    " A.id, A.startTime, A.endTime, A.comment, CY.id, CY.name, P.id, P.name, CO.id, CO.name, TC.id, TC.name, TC.billable, PC.color "
                    "FROM"
                    " ACTUAL AS A,"
                    " PRODUCT AS P,"
                    " TASKCATEGORY AS TC,"
                    " CLIENTUSERS AS CL,"
                    " COMPANY AS CY "
                    "LEFT JOIN"
                    " COMPONENT AS CO "
                    "ON"
                    " A.compid=CO.id "
                    "LEFT JOIN"
                    " PRODUCTCOLOR AS PC "
                    "ON"
                    " P.id=PC.prodid "
                    "WHERE"
                    " A.prodid=P.id and A.cateid=TC.id and P.clientid=CL.id and CL.companyid=CY.id and A.userid='%1' and A.startTime<'%2' and A.endTime>'%3'"
                ).arg(userID_).arg(e.toString(Qt::ISODate)).arg(s.toString(Qt::ISODate));

    QSqlQuery query = db_.exec(q);
    if (query.lastError().isValid()) {
        qDebug() << query.lastError().text();
        return;
    }

    while (query.next()) {
        CalendarItem item;
        item.id = query.value(0).toInt();
        item.stime = query.value(1).toDateTime();
        item.etime = query.value(2).toDateTime();
        item.comment = query.value(3).toString();
        item.companyID = query.value(4).toInt();
        item.company = query.value(5).toString();
        item.prodID = query.value(6).toInt();
        item.product = query.value(7).toString();
        item.compID = query.value(8).toInt();
        item.component = query.value(9).toString();
        item.cateID = query.value(10).toInt();
        item.category = query.value(11).toString();
        item.billable = query.value(12).toBool();
        if (query.value(13).isNull())
            item.color = undefinedColor;
        else {
            item.color.setRgb(query.value(13).toInt());
            item.color.setAlpha(180);
        }
        taskList_[item.id] = item;
    }
}

bool DailyWidget::getTime(const QPointF& p, QDateTime& dateTime)
{
    QDate s = startDate_;
    int i = 0;
    while (s <= endDate_) {
        QRectF taskRect = taskRectList_[i];
        if (taskRect.left() <= p.x() && taskRect.right() >= p.x() && taskRect.top() <= p.y()) {
            int secs;
            if (taskRect.contains(p)) {
                qreal span = p.y() - taskRect.top();
                secs = int(span / heightOfHour_ / timeInterval_) * int(timeInterval_ * 60 * 60);
            }
            else
                secs = 24 * 60 * 60;

            dateTime = QDateTime(s).addSecs(secs);
            return true;
        }

        ++i;
        s = s.addDays(1);
    }
    return false;
}

QList<QRectF> DailyWidget::getTaskRectList(const QDateTime& time1, const QDateTime& time2)
{
    QList<QRectF> result;

    int i = 0;
    QDate s = startDate_;
    while (s <= endDate_) {
        if (time1.date() <= s && time2.date() >= s) {
            int secs;
            qreal h;
            qreal span;
            if (time1.date() < s)
                secs = 0;
            else
                secs = QDateTime(s, QTime(0, 0)).secsTo(time1);
            h =  secs / 60.0 / 60.0;

            if (time2.date() > s)
                span = 24 - h;
            else {
                span = QDateTime(s).addSecs(secs).secsTo(time2) / 60.0 / 60.0;
            }

            qreal y = taskRectList_[i].top() + h * heightOfHour_;
            qreal height = span * heightOfHour_;
            result.push_back( QRectF(taskRectList_[i].left(), y, taskRectList_[i].width(), height) );
        }
        ++i;
        s = s.addDays(1);
    }

    return result;
}

bool DailyWidget::getTaskItems(int x, int y, int* taskID, QList<QGraphicsItem*>* itemList)
{
    if (taskID)
        *taskID = 0;

    if (itemList)
        itemList->clear();

    QGraphicsItem* item = itemAt(x, y);
    if (item == 0)
        return false;

    QGraphicsItemGroup* group = dynamic_cast<QGraphicsItemGroup*>(item->parentItem());
    if (group == 0)
        return false;

    if (taskID)
        *taskID = group->data(0).toInt();

    if (itemList) {
        itemList->push_back(group);
        foreach(QGraphicsItem* child, group->childItems())
            itemList->push_back(child);
    }

    return true;
}

void DailyWidget::previewTask(QDateTime time1, QDateTime time2, const QColor& color, CalendarItem* item)
{
    qDeleteAll(creatingTaskList);
    creatingTaskList.clear();

    if (time1 > time2)
        qSwap(time1, time2);

    QPen bgPen;
    QBrush bgBrush(color);
    QFont font("Helvetica", heightOfHour()*0.25);
    QBrush textBrush( QColor(60, 60, 60) );

    QList<QRectF> taskRectList = getTaskRectList(time1, time2);
    foreach (const QRectF& r, taskRectList) {
        QGraphicsRectItem* bg = scene()->addRect(r, bgPen, bgBrush);
//        GraphicsTextItemSized
        QString timeStr = time1.time().toString("hh:mm:ss") + " - " + time2.time().toString("hh:mm:ss");
        QGraphicsSimpleTextItem* time = scene()->addSimpleText(timeStr, font);
        time->setBrush(textBrush);
        time->setPos(r.left()+4, r.top()+2);

        creatingTaskList.push_back(bg);
        creatingTaskList.push_back(time);

        if (item) {
            QGraphicsSimpleTextItem* product = scene()->addSimpleText(item->product, font);
            product->setBrush(textBrush);
            QRectF r = time->sceneBoundingRect();
            product->setPos(r.left(), r.bottom()+3);
            creatingTaskList.push_back(product);
        }
    }
}

bool DailyWidget::copyTask(int taskID, int& newID)
{
    db_.exec("BEGIN IMMEDIATE");

    const CalendarItem& item = taskList_[taskID];
    QString compid("NULL");
    if (item.compID)
        compid.setNum(item.compID);

    QString q = QString(
                "INSERT INTO ACTUAL"
                "(userid, startTime, endTime, prodid, compid, cateid, comment)"
                " VALUES('%1', '%2', '%3', %4, %5, %6, '%7')")
            .arg(userID_).arg(item.stime.toString(Qt::ISODate)).arg(item.etime.toString(Qt::ISODate)).arg(item.prodID).arg(compid).arg(item.cateID).arg(item.comment);
    QSqlQuery query = db_.exec(q);
    if (query.lastError().isValid()) {
        qDebug() << "copyTask:" << query.lastError().text();
        db_.exec("ROLLBACK");
        return false;
    }

    query = db_.exec("select max(id) from ACTUAL");
    if (query.lastError().isValid()) {
        qDebug() << "copyTask:" << query.lastError().text();
        db_.exec("ROLLBACK");
        return false;
    }

    query.next();
    newID = query.value(0).toInt();
    taskList_[newID] = item;
    taskList_[newID].id = newID;

    db_.exec("COMMIT");
    return true;
}

void DailyWidget::editTask(int taskID)
{
    TaskEditDialog dlg(this, db(), userID(), taskList_[taskID]);
    if (dlg.exec() == QDialog::Accepted) {
        updateItemList();
        createGraphicItems();
    }
}

void DailyWidget::deleteTask(int taskID, bool confirm)
{
    if (SqlHelper().deleteTask(db_, taskID, confirm, this)) {
        updateItemList();
        createGraphicItems();
    }
}

void DailyWidget::updateDatabase(const CalendarItem& item)
{
    QString q = QString(
                "UPDATE ACTUAL"
                " SET startTime='%1', endTime='%2'"
                " WHERE id='%3'")
            .arg(item.stime.toString(Qt::ISODate)).arg(item.etime.toString(Qt::ISODate)).arg(item.id);
    QSqlQuery query = db_.exec(q);
    if (query.lastError().isValid()) {
        qDebug() << "updateDatabase:" << query.lastError().text();
        return;
    }
}

void DailyWidget::on_userID_changed(int userID)
{
    updateItemList();
    createGraphicItems();

    QSqlQuery query = QString("select timeinterval from USERS where id=%1").arg(userID);
    if (query.lastError().isValid()) {
        qDebug() << query.lastError().text();
        return;
    }

    query.next();
    timeInterval_ = query.value(0).toReal();
}

void DailyWidget::on_timeTable_visibled(bool visible)
{
    createGraphicItems();
}

void DailyWidget::on_date_changed(const QDate& startDate, const QDate& endDate)
{
    updateItemList();
    createGraphicItems();
}

void DailyWidget::on_dateFormat_changed(const QString& format)
{
    createGraphicItems();
}

void DailyWidget::on_heightOfHour_changed(qreal height)
{
    createGraphicItems();
    setSceneRect(calendarRect_);
}

void DailyWidget::on_taskEdit_triggered()
{
    editTask(selectingTaskID);
}

void DailyWidget::on_taskDelete_triggered()
{
    deleteTask(selectingTaskID, true);
}

void DailyWidget::on_vscrollbar_valueChanged(int value)
{
    if (verticalScrollBar()->isVisible()) {
        dateBackground_->setRect(dateBackground_->rect().left(), value, dateBackground_->rect().width(), dateBackground_->rect().height());
        foreach(QGraphicsSimpleTextItem* date, dateTextList_) {
            date->setPos(date->pos().x(), value);
        }
    }
}

void DailyWidget::on_sceneRectChanged(const QRectF& r)
{
    qDebug() << "on_sceneRectChanged" << r;
}
